﻿Imports Windows.UI.Xaml.Media.Animation
Imports Windows.UI.Xaml.Media.Imaging

''' <summary>
''' Questa classe fornisce un canvas contenente un background che scorre all'infinito
''' </summary>
''' <remarks></remarks>
Public Class ScrollableBackground
    Implements IDisposable

#Region "DECLARATONS"

    Private _mainCanvas As Canvas = Nothing                 'il controllo Canvas che viene restituito e che contiene l'animazione
    Private _backgroundLayers As List(Of BackgroundLayer)   'lista dei vari livelli per l'effetto di profondità dell'animazione 2D
    Private _scrollSpeed As Double
    Private _collisionDetectionFrequence As Integer    'frequenza di verifica delle collisioni (default 100 millisecondi)

    Private _sprites As List(Of Sprite)                     'lista degli oggetti di tipo Sprite inseriti sopra lo sfondo

    Private LoopStoryboard As Storyboard
    Private CollisionStoryboard As Storyboard

    Private SwitchDirectionStoryboard As Storyboard
    Private SwitchDirectionOriginalSpeed As Integer
    Private SwitchDirectionOffset As Double
    Private SwitchDirectionOffsetTotal As Double
    Private SwitchDirectionNewDirection As enScrollDirection
    Private _isSwitchInProgress As Boolean

    Private _checkCollisionsForSameObjectsType As Boolean       'abilita il controllo delle collisioni anche fra gli oggetti dello stesso tipo

    Private _scrollDirection As enScrollDirection
    Public Enum enScrollDirection
        None = -1
        LeftToRight = 0
        RightToLeft = 1
        TopToBottom = 2
        BottomToTop = 3
    End Enum

    Private _scrollState As enScrollState
    Public Enum enScrollState
        SCROLLING = 0
        PAUSED = 1
        STOPPED = 2
    End Enum

    Private _collisionDetectionLevel As enCollisionDetectionLevel = enCollisionDetectionLevel.DISABLED
    Public Enum enCollisionDetectionLevel
        DISABLED = 0
        AUTO_BOUNDS_ONLY = 1
        AUTO_ADVANCED = 2
        MANUAL_BOUNDS_ONLY = 3
        MANUAL_ADVANCED = 4
    End Enum


#End Region

#Region "PROPERTIES"

    ''' <summary>
    ''' Restituisce il Canvas che viene utilizzato per visualizzare lo scorrimento
    ''' </summary>
    Public ReadOnly Property BackgroundCanvas As Canvas
        Get
            Return _mainCanvas
        End Get
    End Property

    ''' <summary>
    ''' Imposta o restituisce la velocità di scorrimento generale dello sfondo (da 0 a N)
    ''' </summary>
    ''' <value>Valore in secondi</value>
    Public Property ScrollSpeed As Integer
        Get
            Return Me._scrollSpeed
        End Get
        Set(value As Integer)
            Me._scrollSpeed = value
        End Set
    End Property

    ''' <summary>
    ''' Restituisce lo stato attuale dello scorrimento
    ''' </summary>
    Public ReadOnly Property ScrollState As enScrollState
        Get
            Return Me._scrollState
        End Get
    End Property

    ''' <summary>
    ''' Restituisce l'istanza dell'oggetto BackgroundLayer in base al valore della proprietà "Key"
    ''' </summary>
    ''' <param name="key">Stringa che rappresenta la chiave dell'oggetto da ricercare</param>
    ''' <returns>Istanza della classe BackgroundLayer</returns>
    Public ReadOnly Property LayerItem(key As String) As BackgroundLayer
        Get
            If Me._backgroundLayers IsNot Nothing AndAlso Me._backgroundLayers.Count > 0 Then
                For Each layer As BackgroundLayer In Me._backgroundLayers
                    If layer.Key = key Then
                        Return layer
                    End If
                Next layer

                Return Nothing
            Else
                Return Nothing
            End If
        End Get
    End Property
    Public ReadOnly Property LayerItem(index As Integer) As BackgroundLayer
        Get
            If Me._backgroundLayers IsNot Nothing AndAlso Me._backgroundLayers.Count > 0 Then
                Return Me._backgroundLayers(index)
            Else
                Return Nothing
            End If
        End Get
    End Property

    ''' <summary>
    ''' Restituisce la lista degli oggetti sprite
    ''' </summary>
    Public ReadOnly Property Sprites As List(Of Sprite)
        Get
            Return Me._sprites
        End Get
    End Property

    ''' <summary>
    ''' Restituisce l'istanza dell'oggetto Sprite in base al valore della proprietà "Key"
    ''' </summary>
    ''' <param name="key">Stringa che rappresenta la chiave dell'oggetto da ricercare</param>
    ''' <returns>Istanza della classe Sprite</returns>
    Public ReadOnly Property SpriteItem(key As String) As Sprite
        Get
            If Me._sprites IsNot Nothing AndAlso Me._sprites.Count > 0 Then
                For Each s As Sprite In Me._sprites
                    If s.Key = key Then
                        Return s
                    End If
                Next s

                Return Nothing
            Else
                Return Nothing
            End If
        End Get
    End Property

    ''' <summary>
    ''' Restituisce o imposta la direzione di scorrimento
    ''' </summary>
    Public Property ScrollDirection As enScrollDirection
        Get
            Return Me._scrollDirection
        End Get
        Set(value As enScrollDirection)
            Me._scrollDirection = value
        End Set
    End Property

    ''' <summary>
    ''' Restituisce True se è in atto un cambiamento di direzione dello scroll
    ''' </summary>
    Public ReadOnly Property IsSwitchInProgress As Boolean
        Get
            Return Me._isSwitchInProgress
        End Get
    End Property

    ''' <summary>
    ''' Restituisce o imposta la modalità di gestione delle collisioni
    ''' </summary>
    ''' <remarks>Le modalità MANUAL_ non attivano nessun loop di controllo delle collisioni e vano settate se si intende gestire le collisioni nel proprio gameloop mediante il metodo CheckSpritesCollision. 
    ''' Le modalità AUTO_ attivano un loop che gira secondo la proprietà CollisionDetectionFrequence e genera gli eventi CollisionDetect e NoCollision</remarks>
    Public Property CollisionDetectionLevel As enCollisionDetectionLevel
        Get
            Return Me._collisionDetectionLevel
        End Get
        Set(value As enCollisionDetectionLevel)
            Me._collisionDetectionLevel = value
            Select Case Me._collisionDetectionLevel
                Case enCollisionDetectionLevel.DISABLED, enCollisionDetectionLevel.MANUAL_ADVANCED, enCollisionDetectionLevel.MANUAL_BOUNDS_ONLY
                    If Me.CollisionStoryboard IsNot Nothing Then
                        Me.CollisionStoryboard.Stop()
                    End If
                Case Else
                    Me.CollisionStoryboard.Begin()
            End Select
        End Set
    End Property

    ''' <summary>
    ''' Restituisce o imposta la frequenza di verifica delle collisioni in millisecondi
    ''' </summary>
    Public Property CollisionDetectionFrequence As Integer
        Get
            Return Me._collisionDetectionFrequence
        End Get
        Set(value As Integer)
            Me._collisionDetectionFrequence = value
            If Me.CollisionStoryboard IsNot Nothing Then
                Me.CollisionStoryboard.Duration = TimeSpan.FromMilliseconds(value)
            End If
        End Set
    End Property

    ''' <summary>
    ''' Imposta la verifica delle collisioni anche fra gli oggetti Sprite dello stesso tipo
    ''' </summary>
    Public Property CheckCollisionsForSameObjectsType As Boolean
        Get
            Return Me._checkCollisionsForSameObjectsType
        End Get
        Set(value As Boolean)
            Me._checkCollisionsForSameObjectsType = value
        End Set
    End Property

#End Region

#Region "CONSTRUCTORS"

    Public Sub New()
        MyBase.New()

        'imposto i valori di default
        Me.CollisionDetectionFrequence = 100
        Me.CheckCollisionsForSameObjectsType = True

        Me.LoopStoryboard = New Storyboard
        Me.LoopStoryboard.Duration = TimeSpan.FromMilliseconds(0)
        'AddHandler Me.LoopStoryboard.Completed, AddressOf BackgroundLoop

        Me.CollisionStoryboard = New Storyboard
        Me.CollisionStoryboard.Duration = TimeSpan.FromMilliseconds(Me.CollisionDetectionFrequence)

    End Sub

    Public Sub New(width As Double, height As Double, scrollDirection As enScrollDirection)
        MyBase.New()

        'imposto i valori di default
        Me.CollisionDetectionFrequence = 100
        Me.CheckCollisionsForSameObjectsType = True

        Me.LoopStoryboard = New Storyboard
        Me.LoopStoryboard.Duration = TimeSpan.FromMilliseconds(0)
        'AddHandler Me.LoopStoryboard.Completed, AddressOf BackgroundLoop

        Me.CollisionStoryboard = New Storyboard
        Me.CollisionStoryboard.Duration = TimeSpan.FromMilliseconds(Me.CollisionDetectionFrequence)
        'AddHandler Me.CollisionStoryboard.Completed, AddressOf CollisionsLoop

        'istanzio il nuovo canvas
        Me._mainCanvas = New Canvas

        'imposto la direzione di scorrimento iniziale
        Me._scrollDirection = scrollDirection

        'imposto le dimensioni
        Me._mainCanvas.Width = width
        Me._mainCanvas.Height = height

        'le immagini devono stare sempre all'interno del canvas
        'quindi clippo alle sue stesse dimensioni
        Dim rg As New RectangleGeometry
        rg.Rect = New Rect(0, 0, width, height)
        Me._mainCanvas.Clip = rg
        Me._mainCanvas.Background = New SolidColorBrush(Windows.UI.Colors.Transparent)

        Me._scrollState = enScrollState.STOPPED

    End Sub

#End Region

#Region "EVENT DECLARATIONS"

    ''' <summary>
    ''' Evento che viene generato quando 2 oggetti entrano in collisione se il CollisionDetection è attivato
    ''' </summary>
    ''' <param name="sprite1">oggetto 1 della collisione</param>
    ''' <param name="sprite2">oggetto 2 della collisione</param>
    Public Event CollisionDetect(sprite1 As Sprite, sprite2 As Sprite)
    Public Event NoCollision()

#End Region

#Region "Public Methods"

    ''' <summary>
    ''' Aggiunge un layer allo sfondo in ultima posizione
    ''' </summary>
    Public Sub AddLayer(layer As BackgroundLayer)

        If Me._backgroundLayers Is Nothing Then
            Me._backgroundLayers = New List(Of BackgroundLayer)
        End If

        'imposto il riferimento alla classe nel child
        layer.Parent = Me

        'aggiungo alla lista dei livelli
        Me._backgroundLayers.Add(layer)

        For i As Integer = layer.Images.Count - 1 To 0 Step -1
            Me._mainCanvas.Children.Add(layer.Images(i))
        Next

        'inizializzo la posizione delle immagini nel livello
        layer.InitPositions()

    End Sub

    ''' <summary>
    ''' Aggiunge un oggetto Sprite sopra lo sfondo
    ''' </summary>
    Public Sub AddSprite(newSprite As Sprite)

        If Me._sprites Is Nothing Then
            Me._sprites = New List(Of Sprite)
        End If

        'imposto il riferimento alla classe nel child
        newSprite.Parent = Me

        'aggiungo alla lista dei personaggi
        Me._sprites.Add(newSprite)

        'aggiungo la shape al canvas
        Me._mainCanvas.Children.Add(newSprite.SpriteShape)

        'e imposto la posizione iniziale
        Canvas.SetLeft(newSprite.SpriteShape, newSprite.Position.X)
        Canvas.SetTop(newSprite.SpriteShape, newSprite.Position.Y)

        'aggiungo anche i media elements
        If newSprite.SoundEffects IsNot Nothing Then
            For Each media As MediaElement In newSprite.SoundEffects.Values
                Me._mainCanvas.Children.Add(media)
            Next media
        End If

    End Sub

    ''' <summary>
    ''' Rimuove l'oggetto sprite dallo sfondo
    ''' </summary>
    ''' <param name="spriteToRemove">Istanza dell'oggetto Sprite da rimuovere</param>
    Public Sub RemoveSprite(spriteToRemove As Sprite)

        Me._mainCanvas.Children.Remove(spriteToRemove.SpriteShape)
        Me._sprites.Remove(spriteToRemove)

        spriteToRemove.Properties = Nothing

    End Sub

    ''' <summary>
    ''' Avvia lo scroll
    ''' </summary>
    Public Sub StartScroll()

        If Me.ScrollDirection <> enScrollDirection.None Then
            If Me.ScrollState = enScrollState.STOPPED OrElse Me.ScrollState = enScrollState.PAUSED Then

                AddHandler Me.LoopStoryboard.Completed, AddressOf BackgroundLoop
                AddHandler Me.CollisionStoryboard.Completed, AddressOf CollisionsLoop
                Me.LoopStoryboard.Begin()
                Me.CollisionStoryboard.Begin()

                Me._scrollState = enScrollState.SCROLLING
            End If
        End If

    End Sub

    ''' <summary>
    ''' Sospende lo scroll
    ''' </summary>
    Public Sub PauseScroll()

        If Me.ScrollDirection <> enScrollDirection.None Then
            If Me.ScrollState = enScrollState.SCROLLING Then

                RemoveHandler Me.LoopStoryboard.Completed, AddressOf BackgroundLoop
                RemoveHandler Me.CollisionStoryboard.Completed, AddressOf CollisionsLoop
                Me.LoopStoryboard.Stop()
                Me.CollisionStoryboard.Stop()

                Me._scrollState = enScrollState.PAUSED
            End If
        End If

    End Sub

    ''' <summary>
    ''' Arresta lo scroll e riporta lo sfondo allo stato iniziale
    ''' </summary>
    Public Sub StopScroll()

        If Me.ScrollDirection <> enScrollDirection.None Then
            If Me.ScrollState = enScrollState.PAUSED OrElse Me.ScrollState = enScrollState.SCROLLING Then
                RemoveHandler Me.LoopStoryboard.Completed, AddressOf BackgroundLoop
                RemoveHandler Me.CollisionStoryboard.Completed, AddressOf CollisionsLoop
                Me.LoopStoryboard.Stop()
                Me.CollisionStoryboard.Stop()

                'reimposto la posizione iniziale delle immagini
                For Each layer As BackgroundLayer In Me._backgroundLayers
                    layer.InitPositions()
                Next

                Me._scrollState = enScrollState.STOPPED
            End If
        End If

    End Sub

    ''' <summary>
    ''' Modifica la direzione dello scroll dello sfondo con una accelerazione/decelerazione della durata impostata
    ''' </summary>
    ''' <param name="newDirection">Direzione dello scroll che si vuole ottenere</param>
    ''' <param name="durationInSeconds">Durata dell'accelerazione e della decelerazione della transazione in secondi</param>
    ''' <remarks>Impostando la durata a 0 si ottiene una modifica immediata dello scroll</remarks>
    Public Sub SwitchScrollDirection(newDirection As enScrollDirection, durationInSeconds As Double)

        'la nuova direzione deve essere diversa da quella corrente
        If newDirection <> Me.ScrollDirection Then
            _isSwitchInProgress = True

            'se la durata è 0 inverto e basta
            If durationInSeconds <= 0 Then
                Me.ScrollDirection = newDirection
                _isSwitchInProgress = False
            Else
                SwitchDirectionStoryboard = New Storyboard
                SwitchDirectionStoryboard.Duration = TimeSpan.FromSeconds(0.1)
                AddHandler SwitchDirectionStoryboard.Completed, AddressOf SwitchDirectionLoop

                'salvo la velocità iniziale in modo da poterla riportare così alla fine dello scambio
                Me.SwitchDirectionOriginalSpeed = Me.ScrollSpeed
                'salvo la nuova direzione
                Me.SwitchDirectionNewDirection = newDirection
                'calcolo di quanto modificare la velocità ad ogni step
                Me.SwitchDirectionOffset = (Me.ScrollSpeed / durationInSeconds / 10) * 2
                'tengo anche una variabile per sommare gli offset, poichè la proprietà ScrollSpeed è un intero e va decrementato di 1 ogni volta che la somma di SwitchDirectionOffsetTotal raggiunge l'intero (ovvero 1)
                Me.SwitchDirectionOffsetTotal = 0

                SwitchDirectionStoryboard.Begin()
            End If

        End If

    End Sub

    ''' <summary>
    ''' Verifica se i 2 oggetti sprite passati sono in collisione
    ''' </summary>
    ''' <param name="sprite1">Primo oggetto Sprite</param>
    ''' <param name="sprite2">Secondo oggetto Sprite</param>
    ''' <returns>True se i 2 oggetti sono in collisione</returns>
    ''' <remarks>I 2 oggetti devono far parte della collection Sprites dell'istanza di ScrollableBackground</remarks>
    Public Function CheckSpritesCollision(ByRef sprite1 As Sprite, ByRef sprite2 As Sprite) As Boolean

        'i due oggetti devono avere entrambe attivata la collisione
        If sprite1.isCollisionEnabled = True AndAlso sprite2.isCollisionEnabled = True Then
            'non devono essere ovviamente lo stesso oggetto
            If sprite1 IsNot sprite2 Then

                Dim r1 As New Rect(sprite1.Position, sprite1.Size)
                Dim r2 As New Rect(sprite2.Position, sprite2.Size)

                'ricavo il rettangolo di intersezione fra i 2 oggetti
                Dim rInt As New Rect(sprite1.Position, sprite1.Size)
                rInt.Intersect(r2)

                If rInt.IsEmpty = False Then

                    'se il livello di collisione impostato è solo per i contorni, allora basta così
                    If Me.CollisionDetectionLevel = enCollisionDetectionLevel.AUTO_BOUNDS_ONLY OrElse Me.CollisionDetectionLevel = enCollisionDetectionLevel.MANUAL_BOUNDS_ONLY Then
                        Return True
                    Else
                        'se i 2 oggetti si sovrappongono in qualche punto ed è attivato il livello avanzato di collisione, 
                        'verifico se c'è almeno un pixel colorato di s1 che si sovrappone ad un pixel colorato di s2
                        For x As Double = rInt.X To rInt.X + rInt.Width Step 4
                            For y = rInt.Y To rInt.Y + rInt.Height Step 4
                                If sprite1.IsPixelTransparent(New Point(x - sprite1.Position.X, y - sprite1.Position.Y)) = False Then
                                    If sprite2.IsPixelTransparent(New Point(x - sprite2.Position.X, y - sprite2.Position.Y)) = False Then
                                        Return True
                                    End If
                                End If
                            Next y
                        Next x
                    End If

                End If

            End If
        End If

        Return False

    End Function

#End Region

#Region "Private Methods"

    Private Sub BackgroundLoop(sender As Object, e As Object)

        For Each layer As BackgroundLayer In Me._backgroundLayers

            layer.Update()

        Next layer

        Me.LoopStoryboard.Begin()

    End Sub

    Private Sub CollisionsLoop(sender As Object, e As Object)

        CheckCollisions()

        Me.CollisionStoryboard.Begin()

    End Sub

    Private Sub SwitchDirectionLoop(sender As Object, e As Object)

        Me.SwitchDirectionOffsetTotal += Me.SwitchDirectionOffset

        'eseguo un'operazione solo se sono arrivato ad un intero
        If Me.SwitchDirectionOffsetTotal >= 1 Then

            If Me.ScrollDirection <> SwitchDirectionNewDirection Then
                'se sono nella fase di rallentamento, decremento la velocità di scroll
                Me.ScrollSpeed -= SwitchDirectionOffsetTotal

                'se sono arrivato a 0, posso modificare la direzione
                If Me.ScrollSpeed <= 0 Then
                    Me.ScrollDirection = SwitchDirectionNewDirection
                    Me.ScrollSpeed += SwitchDirectionOffsetTotal
                End If

                Me.SwitchDirectionOffsetTotal = 0
                SwitchDirectionStoryboard.Begin()
            Else
                'se sono nella fase di accelerazione, incremento la velocità di scroll
                '(a meno di un passaggio allo stato di non scroll
                If Me.ScrollDirection = enScrollDirection.None Then
                    'se sono nello stato di non scroll fermo tutto e basta
                    SwitchDirectionStoryboard.Stop()
                    _isSwitchInProgress = False
                Else
                    Me.ScrollSpeed += SwitchDirectionOffsetTotal

                    'se ho raggiunto la velocità iniziale, ho finito
                    If Me.ScrollSpeed >= SwitchDirectionOriginalSpeed Then
                        'per sicurezza rimetto esattamente la velocità come all'inizio
                        Me.ScrollSpeed = SwitchDirectionOriginalSpeed
                        SwitchDirectionStoryboard.Stop()
                        _isSwitchInProgress = False
                        RemoveHandler SwitchDirectionStoryboard.Completed, AddressOf SwitchDirectionLoop
                    Else
                        Me.SwitchDirectionOffsetTotal = 0
                        SwitchDirectionStoryboard.Begin()
                    End If
                End If
            End If
        Else
            'se non ho ancora raggiunto l'intero, riparto con il timer e basta
            SwitchDirectionStoryboard.Begin()
        End If

    End Sub

    ''' <summary>
    ''' Routine che verifica tutte le eventuali collisioni fra gli oggetti che hanno la proprietà isCollisionEnabled=True e scatena l'evento CollisionDetect per ogni coppia
    ''' </summary>
    Private Sub CheckCollisions()

        If Me.CollisionDetectionLevel <> enCollisionDetectionLevel.DISABLED AndAlso Me.CollisionDetectionLevel <> enCollisionDetectionLevel.MANUAL_ADVANCED AndAlso Me.CollisionDetectionLevel <> enCollisionDetectionLevel.MANUAL_BOUNDS_ONLY Then
            If _sprites IsNot Nothing AndAlso _sprites.Count > 0 Then
                For Each s1 As Sprite In Me._sprites
                    For Each s2 As Sprite In Me._sprites
                        'i due oggetti devono avere entrambe attivata la collisione
                        If s1.isCollisionEnabled = True AndAlso s2.isCollisionEnabled = True Then
                            'non devono essere ovviamente lo stesso oggetto
                            If s1 IsNot s2 Then
                                'gli oggetti dello stesso tipo li verifico solo se abilitato il flag
                                If Me.CheckCollisionsForSameObjectsType = True OrElse s1.SpriteType <> s2.SpriteType Then

                                    Dim r1 As New Rect(s1.Position, s1.Size)
                                    Dim r2 As New Rect(s2.Position, s2.Size)

                                    'ricavo il rettangolo di intersezione fra i 2 oggetti
                                    Dim rInt As New Rect(s1.Position, s1.Size)
                                    rInt.Intersect(r2)

                                    If rInt.IsEmpty = False Then

                                        'se il livello di collisione impostato è solo per i contorni, allora basta così
                                        If Me.CollisionDetectionLevel = enCollisionDetectionLevel.AUTO_BOUNDS_ONLY Then
                                            RaiseEvent CollisionDetect(s1, s2)
                                            Exit Sub
                                        Else
                                            'se i 2 oggetti si sovrappongono in qualche punto ed è attivato il livello avanzato di collisione, 
                                            'verifico se c'è almeno un pixel colorato di s1 che si sovrappone ad un pixel colorato di s2
                                            For x As Double = rInt.X To rInt.X + rInt.Width Step 4
                                                For y = rInt.Y To rInt.Y + rInt.Height Step 4
                                                    If s1.IsPixelTransparent(New Point(x - s1.Position.X, y - s1.Position.Y)) = False Then
                                                        If s2.IsPixelTransparent(New Point(x - s2.Position.X, y - s2.Position.Y)) = False Then
                                                            RaiseEvent CollisionDetect(s1, s2)
                                                            Exit Sub
                                                        End If
                                                    End If
                                                Next y
                                            Next x
                                        End If

                                    End If
                                End If
                            End If
                        End If
                    Next s2
                Next s1
            End If

            RaiseEvent NoCollision()

        End If

    End Sub

#End Region

#Region "IDisposable Support"
    Private disposedValue As Boolean ' Per rilevare chiamate ridondanti

    ' IDisposable
    Protected Overridable Sub Dispose(disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                RemoveHandler Me.LoopStoryboard.Completed, AddressOf BackgroundLoop
                RemoveHandler Me.CollisionStoryboard.Completed, AddressOf CollisionsLoop
                _mainCanvas = Nothing
                _backgroundLayers.Clear()
                _backgroundLayers = Nothing
                _sprites.Clear()
                _sprites = Nothing
                LoopStoryboard = Nothing
                CollisionStoryboard = Nothing
            End If

            ' TODO: liberare risorse non gestite (oggetti non gestiti) ed eseguire l'override del seguente Finalize().
            ' TODO: impostare campi di grandi dimensioni su null.
        End If
        Me.disposedValue = True
    End Sub

    ' TODO: eseguire l'override di Finalize() solo se Dispose(ByVal disposing As Boolean) dispone del codice per liberare risorse non gestite.
    'Protected Overrides Sub Finalize()
    '    ' Non modificare questo codice. Inserire il codice di pulizia in Dispose(ByVal disposing As Boolean).
    '    Dispose(False)
    '    MyBase.Finalize()
    'End Sub

    ' Questo codice è aggiunto da Visual Basic per implementare in modo corretto il modello Disposable.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Non modificare questo codice. Inserire il codice di pulizia in Dispose(disposing As Boolean).
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class


''' <summary>
''' Questa classe rappresenta un livello di sfondo per la classe ScrollableBackground
''' </summary>
''' <remarks></remarks>
Public Class BackgroundLayer

#Region "DECLARATIONS"

    Private _layerImageSource As BitmapImage            'immagine che compone il livello
    Private _Key As String                              'nome del livello (utile per richiamarlo direttamente dalla classe ScrollableBackground)
    Private _speedRate As Double = 100                  'rapporto di velocità del livello rispetto alla velocità generale dello sfondo in percentuale
    Private _parent As ScrollableBackground = Nothing   'riferimento all'oggetto che contiene il livello
    Private _initialPosition As Point                   'posizione iniziale dell'immagine di sfondo

    Protected Friend Images(1) As Image
    Private CurrentImageIndex As Integer = 0

    Private LayerStoryboard As Storyboard

#End Region

#Region "PROPERTIES"

    ''' <summary>
    ''' Restituisce o imposta il riferimento all'oggetto ScrollableBackground che contiene il livello
    ''' </summary>
    Protected Friend Property Parent As ScrollableBackground
        Get
            Return Me._parent
        End Get
        Set(value As ScrollableBackground)
            Me._parent = value
        End Set
    End Property

    ''' <summary>
    ''' Imposta o restituisce la chiave del livello
    ''' </summary>
    Public Property Key As String
        Get
            Return Me._Key
        End Get
        Set(value As String)
            Me._Key = value
        End Set
    End Property

    ''' <summary>
    ''' Imposta o restituisce il rapporto di velocità di scorrimento rispetto alla velocità generale dello sfondo in percentuale (default 100)
    ''' </summary>
    Public Property SpeedRate As Double
        Get
            Return Me._speedRate
        End Get
        Set(value As Double)
            Me._speedRate = value
        End Set
    End Property

#End Region

#Region "CONSTRUCTORS"

    Public Sub New(initialPosition As Point, width As Double, height As Double, layerImageSource As BitmapImage)

        Me.SetLayerImageSource(layerImageSource, width, height)
        Me._initialPosition = initialPosition

    End Sub

#End Region

#Region "Public Methods"

    ''' <summary>
    ''' Imposta l'origine dell'immagine che compone lo sfondo
    ''' </summary>
    ''' <param name="layerSource">BitmapImage con l'immagine di sfondo</param>
    ''' <param name="width">larghezza dell'immagine</param>
    ''' <param name="height">altezza dell'immagine</param>
    Public Sub SetLayerImageSource(layerSource As BitmapImage, width As Double, height As Double)

        Me._layerImageSource = layerSource

        Dim img As New Image
        img.Width = width
        img.Height = height
        img.CacheMode = New BitmapCache
        img.Source = layerSource
        img.Stretch = Stretch.Fill

        Me.Images(0) = img

        img = New Image
        img.Width = width
        img.Height = height
        img.CacheMode = New BitmapCache
        img.Source = layerSource
        img.Stretch = Stretch.Fill
        Me.Images(1) = img

    End Sub

    ''' <summary>
    ''' Restituisce l'origine dell'immagine che compone lo sfondo
    ''' </summary>
    Public Function GetLayerImageSource() As BitmapImage

        Return Me._layerImageSource

    End Function

#End Region

#Region "Protected Methods"

    ''' <summary>
    ''' Aggiorna l'interfaccia ad ogni game loop
    ''' </summary>
    Protected Friend Sub Update()

        'calcolo di quanto avanzare rispetto alla velocità geneale di scorrimento
        Dim offset As Double = Me.Parent.ScrollSpeed / 100 * Me.SpeedRate

        Select Case Me.Parent.ScrollDirection
            Case ScrollableBackground.enScrollDirection.RightToLeft

                Canvas.SetLeft(Me.Images(0), Me.Images(0).GetValue(Canvas.LeftProperty) - offset)
                Canvas.SetLeft(Me.Images(1), Me.Images(1).GetValue(Canvas.LeftProperty) - offset)

                If Me.Images(0).GetValue(Canvas.LeftProperty) < Me.Images(0).Width * -1 Then
                    Canvas.SetLeft(Me.Images(0), Me.Images(1).GetValue(Canvas.LeftProperty) + Me.Images(1).Width - offset - 1)
                End If
                If Me.Images(1).GetValue(Canvas.LeftProperty) < Me.Images(0).Width * -1 Then
                    Canvas.SetLeft(Me.Images(1), Me.Images(0).GetValue(Canvas.LeftProperty) + Me.Images(0).Width - offset - 1)
                End If

            Case ScrollableBackground.enScrollDirection.LeftToRight

                Canvas.SetLeft(Me.Images(0), Me.Images(0).GetValue(Canvas.LeftProperty) + offset)
                Canvas.SetLeft(Me.Images(1), Me.Images(1).GetValue(Canvas.LeftProperty) + offset)

                If Me.Images(0).GetValue(Canvas.LeftProperty) > Me.Images(0).Width Then
                    Canvas.SetLeft(Me.Images(0), Me.Images(1).GetValue(Canvas.LeftProperty) - Me.Images(1).Width + offset + 1)
                End If
                If Me.Images(1).GetValue(Canvas.LeftProperty) > Me.Images(0).Width Then
                    Canvas.SetLeft(Me.Images(1), Me.Images(0).GetValue(Canvas.LeftProperty) - Me.Images(0).Width + offset + 1)
                End If

            Case ScrollableBackground.enScrollDirection.BottomToTop

                Canvas.SetTop(Me.Images(0), Me.Images(0).GetValue(Canvas.TopProperty) - offset)
                Canvas.SetTop(Me.Images(1), Me.Images(1).GetValue(Canvas.TopProperty) - offset)

                If Me.Images(0).GetValue(Canvas.TopProperty) < Me.Images(0).Height * -1 Then
                    Canvas.SetTop(Me.Images(0), Me.Images(1).GetValue(Canvas.TopProperty) + Me.Images(1).Height - offset - 1)
                End If
                If Me.Images(1).GetValue(Canvas.TopProperty) < Me.Images(0).Height * -1 Then
                    Canvas.SetTop(Me.Images(1), Me.Images(0).GetValue(Canvas.TopProperty) + Me.Images(0).Height - offset - 1)
                End If

            Case ScrollableBackground.enScrollDirection.TopToBottom

                Canvas.SetTop(Me.Images(0), Me.Images(0).GetValue(Canvas.TopProperty) + offset)
                Canvas.SetTop(Me.Images(1), Me.Images(1).GetValue(Canvas.TopProperty) + offset)

                If Me.Images(0).GetValue(Canvas.TopProperty) > Me.Images(0).Height Then
                    Canvas.SetTop(Me.Images(0), Me.Images(1).GetValue(Canvas.TopProperty) - Me.Images(1).Height + offset + 1)
                End If
                If Me.Images(1).GetValue(Canvas.TopProperty) > Me.Images(0).Height Then
                    Canvas.SetTop(Me.Images(1), Me.Images(0).GetValue(Canvas.TopProperty) - Me.Images(0).Height + offset + 1)
                End If

        End Select

    End Sub

    Protected Friend Sub InitPositions()

        Canvas.SetLeft(Me.Images(0), Me._initialPosition.X)
        Canvas.SetTop(Me.Images(0), Me._initialPosition.Y)

        Select Case Me.Parent.ScrollDirection
            Case ScrollableBackground.enScrollDirection.RightToLeft
                Canvas.SetLeft(Me.Images(1), Me._initialPosition.X + Me.Images(0).Width - 1)
                Canvas.SetTop(Me.Images(1), Me._initialPosition.Y)

            Case ScrollableBackground.enScrollDirection.LeftToRight
                Canvas.SetLeft(Me.Images(1), Me._initialPosition.X - Me.Images(0).Width + 1)
                Canvas.SetTop(Me.Images(1), Me._initialPosition.Y)

            Case ScrollableBackground.enScrollDirection.BottomToTop
                Canvas.SetLeft(Me.Images(1), Me._initialPosition.X)
                Canvas.SetTop(Me.Images(1), Me._initialPosition.Y + Me.Images(0).Height - 1)

            Case ScrollableBackground.enScrollDirection.TopToBottom
                Canvas.SetLeft(Me.Images(1), Me._initialPosition.X)
                Canvas.SetTop(Me.Images(1), Me._initialPosition.Y - Me.Images(0).Height + 1)
        End Select


    End Sub

#End Region

End Class
